#ifndef __HARDWARETIMER_H__
#define __HARDWARETIMER_H__
#include "timer.h"
#include "PinNames.h"

#define TIMER_CHANNELS 4

typedef enum
{
    TIMER_DISABLED,
    // Output Compare
    TIMER_OUTPUT_COMPARE,          // == TIM_OCMODE_TIMING           no output, useful for only-interrupt
    TIMER_OUTPUT_COMPARE_ACTIVE,   // == TIM_OCMODE_ACTIVE           pin is set high when counter == channel compare
    TIMER_OUTPUT_COMPARE_INACTIVE, // == TIM_OCMODE_INACTIVE         pin is set low when counter == channel compare
    TIMER_OUTPUT_COMPARE_TOGGLE,   // == TIM_OCMODE_TOGGLE           pin toggles when counter == channel compare
    TIMER_OUTPUT_COMPARE_PWM1,     // == TIM_OCMODE_PWM1             pin high when counter < channel compare, low otherwise
    TIMER_OUTPUT_COMPARE_PWM2,     // == TIM_OCMODE_PWM2             pin low when counter < channel compare, high otherwise
    //* TIMER_OUTPUT_COMPARE_FORCED_ACTIVE,   // == TIM_OCMODE_FORCED_ACTIVE    pin always high
    //* TIMER_OUTPUT_COMPARE_FORCED_INACTIVE, // == TIM_OCMODE_FORCED_INACTIVE  pin always low

    //Input capture
    TIMER_INPUT_CAPTURE_RISING,   // == TIM_INPUTCHANNELPOLARITY_RISING
    TIMER_INPUT_CAPTURE_FALLING,  // == TIM_INPUTCHANNELPOLARITY_FALLING
    TIMER_INPUT_CAPTURE_BOTHEDGE, // == TIM_INPUTCHANNELPOLARITY_BOTHEDGE

    // Used 2 channels for a single pin. One channel in TIM_INPUTCHANNELPOLARITY_RISING another channel in TIM_INPUTCHANNELPOLARITY_FALLING.
    // Channels must be used by pair: CH1 with CH2, or CH3 with CH4
    // This mode is very useful for Frequency and Dutycycle measurement
    //* TIMER_INPUT_FREQ_DUTY_MEASUREMENT,

    TIMER_NOT_USED = 0xFFFF // This must be the last item of this enum
} TimerModes_t;

typedef enum
{
    TICK_FORMAT, // default
    MICROSEC_FORMAT,
    HERTZ_FORMAT,
} TimerFormat_t;

typedef enum
{
    RESOLUTION_1B_COMPARE_FORMAT = 1, // used for Dutycycle: [0 .. 1]
    RESOLUTION_2B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 3]
    RESOLUTION_3B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 7]
    RESOLUTION_4B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 15]
    RESOLUTION_5B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 31]
    RESOLUTION_6B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 63]
    RESOLUTION_7B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 127]
    RESOLUTION_8B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 255]
    RESOLUTION_9B_COMPARE_FORMAT,     // used for Dutycycle: [0 .. 511]
    RESOLUTION_10B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 1023]
    RESOLUTION_11B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 2047]
    RESOLUTION_12B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 4095]
    RESOLUTION_13B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 8191]
    RESOLUTION_14B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 16383]
    RESOLUTION_15B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 32767]
    RESOLUTION_16B_COMPARE_FORMAT,    // used for Dutycycle: [0 .. 65535]

    TICK_COMPARE_FORMAT = 0x80, // default
    MICROSEC_COMPARE_FORMAT,
    HERTZ_COMPARE_FORMAT,
    PERCENT_COMPARE_FORMAT, // used for Dutycycle
} TimerCompareFormat_t;

#ifdef __cplusplus
#include <functional>
using callback_function_t = std::function<void(void)>;

class HardwareTimer
{
public:
    HardwareTimer(TIM_TypeDef *instance);
    ~HardwareTimer();

    void pause(void);
    void pauseChannel(uint32_t channel);
    void resume(void);
    void resumeChannel(uint32_t channel);

    void setPrescaleFactor(uint32_t prescaler);
    uint32_t getPrescaleFactor();

    void setOverflow(uint32_t val, TimerFormat_t format = TICK_FORMAT);
    uint32_t getOverflow(TimerFormat_t format = TICK_FORMAT);

    void setPWM(uint32_t channel, PinName pin, uint32_t frequency, uint32_t dutycycle, callback_function_t PeriodCallback = nullptr, callback_function_t CompareCallback = nullptr);
    void setPWM(uint32_t channel, uint32_t pin, uint32_t frequency, uint32_t dutycycle, callback_function_t PeriodCallback = nullptr, callback_function_t CompareCallback = nullptr);

    void setCount(uint32_t val, TimerFormat_t format = TICK_FORMAT);
    uint32_t getCount(TimerFormat_t format = TICK_FORMAT);

    void setMode(uint32_t channel, TimerModes_t mode, PinName pin = NC);
    void setMode(uint32_t channel, TimerModes_t mode, uint32_t pin);

    TimerModes_t getMode(uint32_t channel);

    void setPreloadEnable(bool value);

    uint32_t getCaptureCompare(uint32_t channel, TimerCompareFormat_t format = TICK_COMPARE_FORMAT);
    void setCaptureCompare(uint32_t channel, uint32_t compare, TimerCompareFormat_t format = TICK_COMPARE_FORMAT);

    void setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority);

    void attachInterrupt(callback_function_t callback);
    void detachInterrupt();
    bool hasInterrupt();

    void attachInterrupt(uint32_t channel, callback_function_t callback);
    void detachInterrupt(uint32_t channel);
    bool hasInterrupt(uint32_t channel);
    void timerHandleDeinit();

    void refresh(void);
    uint32_t getTimerClkFreq();

    static void captureCompareCallback(TIM_TypeDef *htim, uint16_t channel);
    static void updateCallback(TIM_TypeDef *htim);

    int getChannel(uint32_t channel);
    int getIT(uint32_t channel);
    int getITFlag(uint32_t channel);

private:
    void NVICSetPriority(IRQn_Type IRQn, uint32_t preemptPriority, uint32_t subPriority);
private:
    timerObj_t _timerObj;
    callback_function_t callbacks[1 + TIMER_CHANNELS];
    TimerModes_t _ChannelMode[TIMER_CHANNELS];
};

extern timerObj_t *HardwareTimer_Handle[TIMER_NUM];

static inline timer_index_t get_timer_index(TIM_TypeDef *instance)
{
    timer_index_t index = UNKNOWN_TIMER;

#if defined(TIM1_BASE)
    if (instance == TIM1)
    {
        index = TIMER1_INDEX;
    }
#endif
#if defined(TIM2_BASE)
    if (instance == TIM2)
    {
        index = TIMER2_INDEX;
    }
#endif
#if defined(TIM3_BASE)
    if (instance == TIM3)
    {
        index = TIMER3_INDEX;
    }
#endif
#if defined(TIM4_BASE)
    if (instance == TIM4)
    {
        index = TIMER4_INDEX;
    }
#endif
#if defined(TIM5_BASE)
    if (instance == TIM5)
    {
        index = TIMER5_INDEX;
    }
#endif
#if defined(TIM6_BASE)
    if (instance == TIM6)
    {
        index = TIMER6_INDEX;
    }
#endif
#if defined(TIM7_BASE)
    if (instance == TIM7)
    {
        index = TIMER7_INDEX;
    }
#endif
#if defined(TIM8_BASE)
    if (instance == TIM8)
    {
        index = TIMER8_INDEX;
    }
#endif
#if defined(TIM9_BASE)
    if (instance == TIM9)
    {
        index = TIMER9_INDEX;
    }
#endif
#if defined(TIM10_BASE)
    if (instance == TIM10)
    {
        index = TIMER10_INDEX;
    }
#endif
#if defined(TIM11_BASE)
    if (instance == TIM11)
    {
        index = TIMER11_INDEX;
    }
#endif
#if defined(TIM12_BASE)
    if (instance == TIM12)
    {
        index = TIMER12_INDEX;
    }
#endif
#if defined(TIM13_BASE)
    if (instance == TIM13)
    {
        index = TIMER13_INDEX;
    }
#endif
#if defined(TIM14_BASE)
    if (instance == TIM14)
    {
        index = TIMER14_INDEX;
    }
#endif
#if defined(TIM15_BASE)
    if (instance == TIM15)
    {
        index = TIMER15_INDEX;
    }
#endif
#if defined(TIM16_BASE)
    if (instance == TIM16)
    {
        index = TIMER16_INDEX;
    }
#endif
#if defined(TIM17_BASE)
    if (instance == TIM17)
    {
        index = TIMER17_INDEX;
    }
#endif
#if defined(TIM18_BASE)
    if (instance == TIM18)
    {
        index = TIMER18_INDEX;
    }
#endif
#if defined(TIM19_BASE)
    if (instance == TIM19)
    {
        index = TIMER19_INDEX;
    }
#endif
#if defined(TIM20_BASE)
    if (instance == TIM20)
    {
        index = TIMER20_INDEX;
    }
#endif
#if defined(TIM21_BASE)
    if (instance == TIM21)
    {
        index = TIMER21_INDEX;
    }
#endif
#if defined(TIM22_BASE)
    if (instance == TIM22)
    {
        index = TIMER2_INDEX;
    }
#endif
    return index;
}

#endif /* __cplusplus */

#endif // __HARDWARETIMER_H__